﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;

namespace Super.Common.Validate
{
    /* 简单说明
     * ValidateElement  数据验证元素. 针对单一规则以及验证过程,提示信息等. (比如不能为空,数字范围等.)
     * ValidateRules    数据验证规则. 针对单一控件 包含多个验证元素 ValidateElement 此对象为内存驻留对象(将来从配置文件获取)
     * Validate         数据验证对象. 用于在页面辅助生成规则对象ValidateRules和前台Html(后端验证未通过).(razor中书写代码,并根据代码生成规则. 稳定后规则从配置文件获取.)
     * ValidateHelper   数据验证助手. 用于Controller中验证数据是否符合要求.并包含所有页面的所有规则且常驻于内存(不增加内存开销).
     */

    /// <summary> 数据验证前端辅助生成规则对象
    /// </summary>
    public class Validate
    {
        #region 私有成员

        /// <summary> 控件名
        /// </summary>
        private readonly string _controlName;

        /// <summary> 是否为第一个Validate.
        /// </summary>
        private readonly bool _isFirstValidate;

        /// <summary> 数据验证规则
        /// </summary>
        private readonly ValidateRules _validateRules;

        /// <summary> 对象Html
        /// </summary>
        private string _htmlString;

        #endregion

        /// <summary> 构造函数
        /// </summary>
        /// <param name="controlName"></param>
        /// <param name="htmlString"></param>
        public Validate(string controlName = null, string htmlString = null)
        {
#if DEBUG
            if (controlName == null && htmlString == null)
            {
                throw new Exception("构造Validate至少需要传入一个参数!");
            }
#endif

            _htmlString = htmlString;
            _controlName = controlName;

            if (htmlString != null)
            {
                _controlName = GetNameByHtml();
            }

            //第一个Validate控件
            _isFirstValidate = ValidateKey == 0;

            if (_isFirstValidate)
            {
                ValidateKey = ValidateHelper.CreateKey(true);
            }

            _validateRules = new ValidateRules(_controlName);
        }

        /// <summary> Validate Key .
        /// </summary>
        private static int ValidateKey
        {
            get
            {
                return DataCache.HttpContextCache["FirstValidate"] == null
                           ? 0
                           : (int)DataCache.HttpContextCache["FirstValidate"];
            }
            set { DataCache.HttpContextCache["FirstValidate"] = value; }
        }

        /// <summary> 当前验证规则集合
        /// </summary>
        private static List<ValidateRules> NowValidateList
        {
            get
            {
                return ValidateHelper.UserValidateList[ValidateKey];
            }
        }

        /// <summary> 静态构造Validate 用于生成JSon字符串.
        /// </summary>
        /// <param name="controlName"></param>
        /// <returns></returns>
        public static Validate Create(string controlName = null)
        {
            if (controlName == null)
            {
                //这种写法比较不安全,,在项目正式发布时,很可能会因为SourceFile缺失而无法正常执行.
                //所以在项目发布前.需要将 规则信息保存起来.
                //项目发布后这里以及Validate中的大多数代码无需执行.
                //Validate 的规则方法全部废止掉. 只要保留ToHtml的功能即可. 该功能根据配置信息生成Json字符串.

                var st = new StackTrace(true);
                StackFrame fs = st.GetFrame(1);
                if (fs != null)
                {
                    string[] ls = File.ReadAllLines(fs.GetFileName());
                    string line = ls[fs.GetFileLineNumber() - 1];
                    int inx = line.IndexOf("name=\"", StringComparison.OrdinalIgnoreCase) + 6;
                    if (inx > 5)
                    {
                        controlName = line.Substring(inx);
                        controlName = controlName.Remove(controlName.IndexOf('"'));
                    }
                    else
                    {
                        throw new Exception("name 属性缺失.");
                    }
                }
            }

            return new Validate(controlName);
        }

        /// <summary> 不可为空
        /// </summary>
        /// <returns></returns>
        public Validate Required(string errorMessage = " 不可为空")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("required", "true", errorMessage,
                                                                       j => !string.IsNullOrEmpty(j)));
            return this;
        }

        /// <summary> 远端Ajax验证 如使用此功能,需继承CommonController
        /// </summary>
        /// <param name="fc"></param>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Code(Func<string, object> fc, string errorMessage = null)
        {
            int pageKey = ValidateKey;
            string controlKey = _controlName;
            string url = "/ValidateCode?p=" + pageKey + "&c=" + HttpContext.Current.Server.UrlEncode(controlKey);

            if (errorMessage != null)
            {
                _validateRules.ValidateElementList.Add(ValidateElement.CreateRemoteValidate("remote", "'" + url + "'", j => (bool)fc(j) ? "True" : errorMessage));
            }
            else
            {
                _validateRules.ValidateElementList.Add(ValidateElement.CreateRemoteValidate("remote", "'" + url + "'", fc));
            }

            //_validateRules.ValidateElementList.Add(new ValidateElement("remote", "'" + url + "'", errorMessage, fc)); 
            return this;
        }


        /// <summary> 请输入合法的网址
        /// </summary>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Url(string errorMessage = " 请输入网址格式.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("url", "true", errorMessage,
                                                                       j =>
                                                                       new Regex("http://([^ ,\r\n]*)").Match(j).Success));
            return this;
        }

        /// <summary> 请输入合法的日期
        /// </summary>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Date(string errorMessage = " 请输入正确的日期格式.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("date", "true", errorMessage, j =>
                                                                                                         {
                                                                                                             try
                                                                                                             {
                                                                                                                 DateTime
                                                                                                                     .
                                                                                                                     Parse
                                                                                                                     (j);
                                                                                                                 return
                                                                                                                     true;
                                                                                                             }
                                                                                                             catch
                                                                                                             {
                                                                                                                 return
                                                                                                                     false;
                                                                                                             }
                                                                                                         }));
            return this;
        }

        /// <summary> 请输入合法的数字
        /// </summary>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Number(string errorMessage = " 只能输入数字格式.")
        {
            _validateRules.ValidateElementList.Add
                (new ValidateElement
                    ("number", "true", errorMessage,
                    j =>
                    {
                        try
                        {
                            double.Parse(j);
                            return
                                true;
                        }
                        catch
                        {
                            return
                                false;
                        }
                    }));
            return this;
        }

        /// <summary>
        /// 输入格式正确的金额
        /// </summary>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Money(string errorMessage = "您输入的金额格式不正确.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("money", "true", errorMessage,
                                                                       j =>
                                                                       new Regex(
                                                                           @"^[+]?(([1-9]\d*[.]?)|(0.))(\d{0,2})?$").
                                                                           Match(j).Success));
            return this;
        }

        /// <summary>
        /// 验证身份证号码格式
        /// </summary>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Idcardno(string errorMessage = "您输入的身份证号码格式不正确!")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("idcardno", "true", errorMessage,
                                                                       j => { return CheckIDCard(j); }));
            return this;
        }

        /// <summary> 只能输入整数
        /// </summary>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Digits(string errorMessage = "只能输入整数.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("digits", "true", errorMessage, j =>
            {
                try
                {
                    int.Parse(j);
                    return
                        j.IndexOf(".") <0;
                }
                catch
                {
                    return
                        false;
                }
            }));
            return this;
        }

        /// <summary> 请输入合法的信用卡号
        /// </summary>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Creditcard(string errorMessage = "请输入合法的信用卡号.")
        {
            _validateRules.ValidateElementList.Add
                (new ValidateElement
                    ("creditcard", "true", errorMessage,
                    j =>
                    {
                        try
                        {
                            long.Parse(j);
                            return
                                j.IndexOf(".") <0 &&j.Length ==19;
                        }
                        catch
                        {
                            return
                                false;
                        }
                    }));
            return this;
        }

        /// <summary> 和表单中的一个元素的值比较
        /// </summary>
        /// <param name="inputName">input name</param>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate EqualTo(string inputName, string errorMessage = " 请再次输入相同的值.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("equalTo", inputName, errorMessage,
                                                                       (j, k) =>
                                                                       j ==
                                                                       HttpContext.Current.Request.Form[
                                                                           k.Replace("'", "").TrimStart('#')]));
            //取另一个表单的value，先去掉引号和#号
            return this;
        }

        /// <summary> 请输入拥有合法后缀名的字符串
        /// </summary>
        /// <param name="value"></param>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Accept(string value, string errorMessage = "请输入拥有合法后缀名的字符串.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("accept", value, errorMessage,
                                                                       (j, k) => j.EndsWith(k)));
            return this;
        }

        /// <summary> 请输入一个手机号码
        /// </summary>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Mobile(string errorMessage = "请输入一个手机号码.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("mobile", "true", errorMessage,
                                                                       j =>
                                                                       new Regex(@"^1[3|4|5|8][0-9]\d{4,8}$").Match(j).
                                                                           Success));
            return this;
        }

        /// <summary> 请输入一个QQ
        /// </summary>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate QQ(string errorMessage = "请输入一个QQ号.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("qq", "true", errorMessage,
                                                                       j =>
                                                                       new Regex(@"^[1-9]{1}[0-9]{4,11}|([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})$").Match(j).
                                                                           Success));
            return this;
        }

        /// <summary> 请输入一个微信号
        /// </summary>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate MicroChannel(string errorMessage = "请输入一个微信账号.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("microchannel", "true", errorMessage,
                                                                       j =>
                                                                       new Regex(@"^[1-9]{1}[0-9]{4,11}|([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})|[a-zA-Z0-9_]$").Match(j).
                                                                           Success));
            return this;
        }

        /// <summary> 请输入一个最小为{0}的值
        /// </summary>
        /// <param name="value"></param>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Min(double value, string errorMessage = "请输入一个最小为{0}的值.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("min", value.ToString(), errorMessage,
                                                                       (j, k) => double.Parse(j) >= double.Parse(k)));
            return this;
        }

        /// <summary> 请输入一个最大为{0}的值
        /// </summary>
        /// <param name="value"></param>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Max(double value, string errorMessage = "请输入一个最大为{0}的值.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("max", value.ToString(), errorMessage,
                                                                       (j, k) => double.Parse(j) <= double.Parse(k)));
            return this;
        }

        /// <summary> 请输入一个介于 {0} 和 {1} 之间的值
        /// </summary>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Range(double min, double max, string errorMessage = "请输入一个介于 {0} 和 {1} 之间的值.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("range",
                                                                       "[" + min.ToString() + "," + max.ToString() + "]",
                                                                       errorMessage, (j, k) =>
                                                                                         {
                                                                                             List<double> l =
                                                                                                 k.Trim(
                                                                                                     "[]".ToCharArray())
                                                                                                     .Split(',').Select(
                                                                                                         q =>
                                                                                                         double.Parse(q))
                                                                                                     .ToList();
                                                                                             double d = double.Parse(j);
                                                                                             return d >= l[0] &&
                                                                                                    d <= l[1];
                                                                                         }));
            return this;
        }

        /// <summary> 请输入一个长度最少是 {0} 的字符串
        /// </summary>
        /// <param name="minLength"></param>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Minlength(int minLength, string errorMessage = " 长度至少需要{0}个字.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("minlength", minLength.ToString(), errorMessage,
                                                                       (j, k) => j.Length >= int.Parse(k)));
            return this;
        }

        /// <summary> 请输入一个长度最多是 {0} 的字符串
        /// </summary>
        /// <param name="maxLength"></param>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Maxlength(int maxLength, string errorMessage = " 长度最大只能{0}个字.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("maxlength", maxLength.ToString(), errorMessage,
                                                                       (j, k) => j.Length <= int.Parse(k)));
            return this;
        }

        /// <summary> 请输入一个长度介于 {0} 和 {1} 之间的字符串
        /// </summary>
        /// <param name="rangelength"></param>
        /// <param name="maxLength"></param>
        /// <param name="errorMessage"></param>
        /// <param name="minLength"></param>
        /// <returns></returns>
        public Validate Rangelength(int minLength, int maxLength, string errorMessage = "请输入一个长度介于 {0} 和 {1} 之间的字符串.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("rangelength",
                                                                       "[" + minLength.ToString() + "," +
                                                                       maxLength.ToString() + "]", errorMessage,
                                                                       (j, k) =>
                                                                       {
                                                                           List<int> l =
                                                                               k.Trim("[]".ToCharArray()).Split(',')
                                                                                   .Select(int.Parse).ToList
                                                                                   ();
                                                                           return j.Length >= l[0] &&
                                                                                  j.Length <= l[1];
                                                                       }));
            return this;
        }

        /// <summary> 必须是Email
        /// </summary>
        /// <returns></returns>
        public Validate Email(string errorMessage = "请输入正确格式的电子邮件.")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("email", "true", errorMessage,
                                                                       j =>
                                                                       new Regex(@"^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$")
                                                                           .Match(j).Success));
            return this;
        }

        /// <summary>
        /// 必须是中文汉字字符
        /// </summary>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        public Validate Chinese(string errorMessage = "请输入汉字")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("chinese", "true", errorMessage,
                                                                       j =>
                                                                       new Regex("^[\u4e00-\u9fa5]+$").Match(j).Success));
            return this;
        }

        public Validate CheckUserName(string errorMessage = "用户名只能输入汉字，数字，英文字母和下划线")
        {
            _validateRules.ValidateElementList.Add(new ValidateElement("CheckUserName", "true", errorMessage,
                                                                      j =>
                                                                      new Regex(@"^[\u4E00-\u9FA5\w\d]+$").Match(j).Success));
            return this;
        }

        /// <summary> 根据Html获取Name
        /// </summary>
        private string GetNameByHtml()
        {
            int inx = _htmlString.IndexOf("name=\"") + 6;
            return _htmlString.Substring(inx, _htmlString.IndexOf("\"", inx) - inx);
        }

        /// <summary> 生成Html 和 规则
        /// </summary>
        /// <returns></returns>
        public MvcHtmlString ToHtml()
        {
            //增加规则.
            var nv = NowValidateList;

            var nr = nv.FirstOrDefault(j => j.ControlName == _controlName);

            //不重复增加规则     
            if (nr == null)
            {
                nv.Add(_validateRules);
            }
            //#if DEBUG   //为了测试,,不同页面生成同一个Post地址的规则,以第一个页面为准.
            else
            {
                //调试阶段及时更新规则
                nr.ValidateElementList = _validateRules.ValidateElementList;
            }
            //#endif

            string resultErrorMessage;
            bool isError = !_validateRules.Check(out resultErrorMessage);

            if (_htmlString != null)
            {
                int inx;

                //select 控件
                if ((inx = _htmlString.IndexOf("><option")) > 0)
                {
                    _htmlString = _htmlString.Insert(inx, _validateRules.GetJQueryValidateString()) +
                                  (isError ? "<label class=\"error\">" + resultErrorMessage + "</label>" : "");
                }
                else if ((inx = _htmlString.IndexOf("/>")) > 0)
                {
                    //TextFor Html 
                    _htmlString = _htmlString.Insert(inx,
                                                     _validateRules.GetJQueryValidateString()) +
                                  (isError ? "<label class=\"error\">" + resultErrorMessage + "</label>" : "");
                }
                else if ((inx = Math.Max(_htmlString.IndexOf(">\r\n</textarea>"), _htmlString.IndexOf("></textarea>"))) >
                         0)
                {
                    //TextFor Html 
                    _htmlString = _htmlString.Insert(inx,
                                                     _validateRules.GetJQueryValidateString()) +
                                  (isError ? "<label class=\"error\">" + resultErrorMessage + "</label>" : "");
                }
            }
            else
            {
                //Input Html
                _htmlString = _validateRules.GetJQueryValidateString() +
                              (isError ? "/><label class=\"error\">" + resultErrorMessage + "</label><a " : "");
            }

            //输出Html
            return new MvcHtmlString(_htmlString + (_isFirstValidate ? GetValidateKeyHtml() : ""));
        }

        /// <summary> 得到一个ValidateKey.
        /// </summary>
        /// <returns></returns>
        private static string GetValidateKeyHtml()
        {
            if (DataCache.HttpContextCache["isResponseValidate"] == null)
            {
                DataCache.HttpContextCache["isResponseValidate"] = true;
                return "<input name=\"__ValidateKey\" type=\"hidden\" value=\"" + ValidateKey + "\"/></input><script src=\"/Assets/script/validate.js\"></script>";
            }
            return "<input name=\"__ValidateKey\" type=\"hidden\" value=\"" + ValidateKey + "\"/></input>";
        }

        /// <summary> 生成一个验证防重复提交的Key,
        /// 在一个页面有多个Form 或着 Form 需要提交到其他 Action 的情况下,需要在Form下第一行增加此代码.
        /// 其他情况调用会导致不可控的风险.
        /// <param name="tagerUrl">目标Url, 不可以有"..","~" 字符. 不需要但可以包括参数. 可以有"/"</param>
        /// <param name="isOneTime">一次性使用的Key</param>
        /// </summary>
        public static HtmlString CreateValidateKey(string tagerUrl = null, bool isOneTime = true)
        {
            ValidateKey = ValidateHelper.CreateKey(isOneTime, tagerUrl);

            return new HtmlString(GetValidateKeyHtml());
        }

        /// <summary> 生成一个验证防重复提交的Key,
        /// 在一个页面有多个Form 或着 Form 需要提交到其他 Action 的情况下,需要在Form下第一行增加此代码.
        /// 其他情况调用会导致不可控的风险.
        /// </summary>
        /// <param name="isOneTime">一次性使用的Key</param>
        /// <returns></returns>
        public static HtmlString CreateValidateKey(bool isOneTime)
        {
            return CreateValidateKey(null, isOneTime);
        }

        /// <summary>
        /// 验证身份证号码格式
        /// </summary>
        /// <param name="Id"></param>
        /// <returns></returns>
        private bool CheckIDCard(string Id)
        {
            if (Id.Length == 18)
            {
                bool check = CheckIDCard18(Id);
                return check;
            }
            else if (Id.Length == 15)
            {
                bool check = CheckIDCard15(Id);
                return check;
            }
            else
            {
                return false;
            }
        }

        /// <summary>  验证18位身份证号码
        /// </summary>
        /// <param name="Id"></param>
        /// <returns></returns>
        // ReSharper disable MemberCanBeMadeStatic.Local
        private bool CheckIDCard18(string Id)
        // ReSharper restore MemberCanBeMadeStatic.Local
        {
            long n = 0;
            if (long.TryParse(Id.Remove(17), out n) == false || n < Math.Pow(10, 16) ||
                long.TryParse(Id.Replace('x', '0').Replace('X', '0'), out n) == false)
            {
                return false; //数字验证 
            }
            string address =
                "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91";
            if (address.IndexOf(Id.Remove(2)) == -1)
            {
                return false; //省份验证 
            }
            string birth = Id.Substring(6, 8).Insert(6, "-").Insert(4, "-");
            var time = new DateTime();
            if (DateTime.TryParse(birth, out time) == false)
            {
                return false; //生日验证 
            }
            string[] arrVarifyCode = ("1,0,x,9,8,7,6,5,4,3,2").Split(',');
            string[] Wi = ("7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2").Split(',');
            char[] Ai = Id.Remove(17).ToCharArray();
            int sum = 0;
            for (int i = 0; i < 17; i++)
            {
                sum += int.Parse(Wi[i]) * int.Parse(Ai[i].ToString());
            }
            int y = -1;
            Math.DivRem(sum, 11, out y);
            if (arrVarifyCode[y] != Id.Substring(17, 1).ToLower())
            {
                return false; //校验码验证 
            }
            return true; //符合GB11643-1999标准 
        }

        /// <summary>  验证15位身份证号码
        /// </summary>
        /// <param name="Id"></param>
        /// <returns></returns>
        // ReSharper disable MemberCanBeMadeStatic.Local
        private bool CheckIDCard15(string Id)
        // ReSharper restore MemberCanBeMadeStatic.Local
        {
            long n = 0;
            if (long.TryParse(Id, out n) == false || n < Math.Pow(10, 14))
            {
                return false; //数字验证 
            }
            string address =
                "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91";
            if (address.IndexOf(Id.Remove(2)) == -1)
            {
                return false; //省份验证 
            }
            string birth = Id.Substring(6, 6).Insert(4, "-").Insert(2, "-");
            var time = new DateTime();
            if (DateTime.TryParse(birth, out time) == false)
            {
                return false; //生日验证 
            }
            return true; //符合15位身份证标准 
        }
    }
}